package nl.saxion.security.encryption;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.io.OutputStream;

import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;

import javax.crypto.spec.SecretKeySpec;

import nl.saxion.security.synchronize.FileIncHash;

/**
 * 
 *  @author EII7SCb
 *
 */
public class TransformerAES {

	private Cipher cipher;
	private final String algorithm = "AES";
	

	public TransformerAES() {
		try {
			Cipher.getInstance("AES/CBC/PKCS5Padding");
		} catch (NoSuchAlgorithmException e) {

			e.printStackTrace();
		} catch (NoSuchPaddingException e) {

			e.printStackTrace();
		}
	}
	

	/**
	 * Turns array of bytes into string
	 * 
	 * @param buf
	 *            Array of bytes to convert to hex string
	 * @return Generated hex string
	 */
	public String asHex(byte buf[]) {
		StringBuffer strbuf = new StringBuffer(buf.length * 2);
		int i;

		for (i = 0; i < buf.length; i++) {
			if (((int) buf[i] & 0xff) < 0x10)
				strbuf.append("0");

			strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
		}

		return strbuf.toString();
	}

	/**
	 * Turns an hex string into an array of bytes. Does the opposite of asHex()
	 * 
	 * @param hextring
	 * @return Array of bytes to convert to hex string
	 */
	public byte[] hexStringToByteArray(String hexstring) {
		byte[] b = new byte[hexstring.length() / 2];
		for (int i = 0; i < b.length; i++) {
		  int index = i * 2;
		  int v = Integer.parseInt(hexstring.substring(index, index + 2), 16);
		  b[i] = (byte) v;
		}
		return b;
	}

	
	
	/**
	 * Generates a hash from a file
	 * 
	 * @param fileLocation
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */

	public String generateHash(String fileLocation)
			throws NoSuchAlgorithmException, IOException {

		MessageDigest md = MessageDigest.getInstance("SHA1");
		FileInputStream fis = new FileInputStream(fileLocation);
		byte[] dataBytes = new byte[1024];

		int nread = 0;

		while ((nread = fis.read(dataBytes)) != -1) {
			md.update(dataBytes, 0, nread);
		}
		

		byte[] mdbytes = md.digest();
		fis.close();
		return asHex(mdbytes);
	}
	

	/**
	 * Encrypts a file
	 * 
	 * @param plaintext
	 * @return
	 * @throws Exception
	 */
	public byte[] encryptText(byte[] key, String plaintext) throws Exception {
		byte[] encrypted = null;
			
		initCipher(Cipher.ENCRYPT_MODE, key);

		encrypted = cipher.doFinal(plaintext.getBytes());
		return encrypted;

		// return encrypted;
	}

	/**
	 * Decrypt a file
	 * 
	 * @param ciphertext
	 * @return
	 * @throws Exception
	 */
	public String decryptText(byte[] key, byte[] ciphertext) throws Exception {

		String originalString = null;

		initCipher(Cipher.DECRYPT_MODE, key);

		byte[] original = cipher.doFinal(ciphertext);
		originalString = new String(original);
		return originalString;

	}

	/**
	 * Encrypts a file
	 * 
	 * @param key
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	public void encryptFile(byte[] key, String file, String newfile) throws Exception {
		initCipher(Cipher.ENCRYPT_MODE, key);
		transform(file, newfile);
	}
	
	/**
	 * Encrypts a file from an outputstream
	 * @param key
	 * @param file
	 * @param out
	 * @throws Exception
	 */
	public void encryptFile2(byte[] key, String file, OutputStream out) throws Exception {
		initCipher(Cipher.ENCRYPT_MODE, key);
		transform2(file, out);
	}
	/**
	 * Encrypts a file and returns it
	 * 
	 * @param key
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	public byte[] encryptFileReturn(byte[] key, File myFile) throws Exception {
		byte[] encryptedFile = null;
		initCipher(Cipher.ENCRYPT_MODE, key);
		encryptedFile = transformFile(myFile);
		return encryptedFile;
	}
	/**
	 * Decrypts a file
	 * 
	 * @param key
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	public void decryptFile(byte[] key, String file, String newfile) throws Exception {
		initCipher(Cipher.DECRYPT_MODE, key);
		transform(file, newfile);
	}
	
	/**
	 * Encrypts a file from an outputstream
	 * @param key
	 * @param file
	 * @param out
	 * @throws Exception
	 */
	public void decryptFile2(byte[] key, String file,OutputStream out) throws Exception {
		initCipher(Cipher.DECRYPT_MODE, key);
		transform2(file, out);

	}
	/**
	 * Decrypts a file and returns it
	 * 
	 * @param key
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	public byte[] decryptFileReturn(byte[] key, File myFile) throws Exception {

		byte[] decryptedFile = null;
		initCipher(Cipher.DECRYPT_MODE, key);
		decryptedFile = transformFile(myFile);
		return decryptedFile;

	}	


	/**
	 * Decrypts bytes to bytes
	 * @param key
	 * @param plainbytes
	 * @return
	 * @throws Exception
	 */
	public byte[] decryptBytes(byte[] key, byte[] ciphertext) throws Exception {

		initCipher(Cipher.DECRYPT_MODE, key);

		byte[] original = cipher.doFinal(ciphertext);
		
		return original;

	}
	
	/**
	 * Encrypts bytes to bytes
	 * @param key
	 * @param plainbytes
	 * @return
	 * @throws Exception
	 */
	public byte[] encryptBytes(byte[] key, byte[] plainbytes) throws Exception {
		byte[] encrypted = null;
			
		initCipher(Cipher.ENCRYPT_MODE, key);

		encrypted = cipher.doFinal(plainbytes);
		return encrypted;

		// return encrypted;
	}
	
	
	/**
	 * Generates a random salt with 128 bits
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public byte[] generateRandomSalt() throws NoSuchAlgorithmException{
		KeyGenerator kgen = KeyGenerator.getInstance("AES");
		kgen.init(128); // 192 and 256 bits may not be available

		// Generate the secret key specs.
		SecretKey skey = kgen.generateKey();
		byte[] raw = skey.getEncoded();
		return raw;
	}
	
	/**
	 * Generates a 256bit key from a given input string. It uses SHA-256 to generate a hash from this input string.
	 * 
	 * @param theKey
	 * @return
	 * @throws Exception
	 */
	public byte[] generateKey(String theKey) throws Exception {
		byte[] rawKey = null;

		MessageDigest md = MessageDigest.getInstance("SHA-256");
		md.update(theKey.getBytes("UTF-8"), 0, theKey.length());
	
        rawKey = md.digest();
		return rawKey;

	}
	
	/**
	 * Generates a random 256bit key
	 * 
	 * @return
	 * @throws Exception
	 */
	public byte[] generateRandomKey() throws Exception {
		KeyGenerator kgen = KeyGenerator.getInstance("AES");
		kgen.init(256); // 192 and 256 bits may not be available

		// Generate the secret key specs.
		SecretKey skey = kgen.generateKey();
		byte[] raw = skey.getEncoded();
		return raw;
	}
	

	/**
	 * Initializes the Cipher
	 * 
	 * @param mode
	 * @param theKey
	 * @throws Exception
	 */
	private void initCipher(int mode, byte[] theKey) throws Exception {

		SecretKeySpec keyspec = new SecretKeySpec(theKey, algorithm);
		cipher = Cipher.getInstance(algorithm);
		cipher.init(mode, keyspec);

	}

	/**
	 * Transforms a file
	 * 
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	private void transform(String file, String newfile) throws Exception {

		byte[] plainData;
		byte[] encryptedData;

		File f = new File(file);
		FileInputStream in;

		try {

			in = new FileInputStream(f);

			plainData = new byte[(int) f.length()];

			in.read(plainData);

			encryptedData = cipher.doFinal(plainData);

			FileOutputStream target;

			target = new FileOutputStream(new File(newfile));

			target.write(encryptedData);
			target.close();
			in.close();

		} catch (Exception e) {
			
			throw e;
		}
	}
	
	/**
	 * Transforms a file
	 * @param file
	 * @param out
	 * @throws Exception
	 */
	private void transform2(String file, java.io.OutputStream out) throws Exception {

		byte[] plainData;
		byte[] encryptedData;

		File f = new File(file);
		FileInputStream in;

		try {

			in = new FileInputStream(f);

			plainData = new byte[(int) f.length()];

			in.read(plainData);

			encryptedData = cipher.doFinal(plainData);

			java.io.OutputStream target = out;

			target.write(encryptedData);
			target.close();
			in.close();

		} catch (Exception e) {
			
			throw e;
		}
	}

	/**
	 * Encrypts a object
	 * 
	 * @param key
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	public SealedObject encryptObject(byte[] key, Object myObject) throws Exception {
				
		initCipher(Cipher.ENCRYPT_MODE, key);
		SealedObject returnObj = new SealedObject( (Serializable) myObject, cipher);
		return returnObj;

	}
	
	public SealedObject encryptObject2(byte[] key, ArrayList<FileIncHash> list) throws Exception {
		
		initCipher(Cipher.ENCRYPT_MODE, key);
		SealedObject returnObj = new SealedObject( list, cipher);
		return returnObj;
	}

	/**
	 * Decrypts a object
	 * 
	 * @param key
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	public Object decryptObject(byte[] key, SealedObject sObject) throws Exception {

		Object returnObj = null;
		initCipher(Cipher.DECRYPT_MODE, key);
		returnObj = sObject.getObject(cipher);
		return returnObj;

	}
	
	/**
	 * Transforms a file
	 * 
	 * @param file
	 * @param newfile
	 * @throws Exception
	 */
	private byte[] transformFile(File file) throws Exception {

		byte[] plainData;
		byte[] encryptedData;

		File f = file;
		FileInputStream in;

		try {

			in = new FileInputStream(f);

			plainData = new byte[(int) f.length()];

			in.read(plainData);
			encryptedData = cipher.doFinal(plainData);

			return encryptedData;

		} catch (Exception e) {

			throw e;
		}
	}



}

